package com.jxpanda.infrastructure.crypto.algorithm.encrypt;

import com.jxpanda.infrastructure.crypto.algorithm.encrypt.contract.AbstractAsymmetricEncrypt;
import com.jxpanda.infrastructure.crypto.constant.AlgorithmEncrypt;
import com.jxpanda.infrastructure.crypto.toolkit.CryptoProviderLoadKit;
import org.bouncycastle.jce.spec.IESParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
import java.util.Objects;

public class EcEncrypt extends AbstractAsymmetricEncrypt {

    static {
        CryptoProviderLoadKit.load();
    }

    private final IESParameterSpec iesParameterSpec;

    public EcEncrypt(AlgorithmEncrypt.EC ec, KeyPair keyPair) {
        super(ec.getAlgorithm(), keyPair);
        this.iesParameterSpec = createIesParameterSpec(ec);
    }

    public EcEncrypt(AlgorithmEncrypt.EC ec, PublicKey publicKey) {
        super(ec.getAlgorithm(), publicKey);
        this.iesParameterSpec = createIesParameterSpec(ec);
    }

    public EcEncrypt(AlgorithmEncrypt.EC ec, PrivateKey privateKey) {
        super(ec.getAlgorithm(), privateKey);
        this.iesParameterSpec = createIesParameterSpec(ec);
    }

    public static IESParameterSpec createIesParameterSpec(AlgorithmEncrypt.EC ec) {
        if (ec == AlgorithmEncrypt.EC.ECIES) {
            return null;
        }
        SecureRandom secureRandom = new SecureRandom();
        // IES 参数：派生参数、编码参数、MAC 长度、加密密钥长度、可选 nonce
        byte[] nonce = secureRandom.generateSeed(AlgorithmEncrypt.EC.DEFAULT_SECURE_LENGTH);
        return new IESParameterSpec(null, null, ec.getMacKeySize(), ec.getCipherKeySize(), nonce);
    }

    @Override
    public Cipher createCipher(int mode) {
        try {
            Cipher cipher = Cipher.getInstance(getAlgorithm());
            if (mode == Cipher.ENCRYPT_MODE) {
                PublicKey publicKey = getPublicKey();
                Objects.requireNonNull(publicKey, "The public key can not be null");
                cipher.init(mode, publicKey, iesParameterSpec);
            } else if (mode == Cipher.DECRYPT_MODE) {
                PrivateKey privateKey = getPrivateKey();
                Objects.requireNonNull(privateKey, "The private key can not be null");
                cipher.init(mode, privateKey, iesParameterSpec);
            }
            return cipher;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
                 InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }
    }

    public static EcEncrypt createDefault() {
        AlgorithmEncrypt.EC ecAlgorithm = AlgorithmEncrypt.EC.ECIES_WITH_SHA256;
        KeyPair keyPair = ecAlgorithm.keyPairProvider().generateKey();
        return new EcEncrypt(ecAlgorithm, keyPair);
    }

    public static Builder builder(AlgorithmEncrypt.EC ec) {
        return new Builder(ec);
    }

    public static class Builder extends AbstractAsymmetricEncrypt.Builder<AlgorithmEncrypt.EC, EcEncrypt> {
        protected Builder(AlgorithmEncrypt.EC ec) {
            super(ec);
        }
    }

}
